﻿using System;
using CoreRay;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace CoreRay.Testing
{
    //-----------------------------------------------------------------------------------------
    /// <summary>
    /// This is a test class for MatrixTest and is intended to contain all MatrixTest 
    /// Unit Tests.
    /// </summary>
    //-----------------------------------------------------------------------------------------
    [TestClass]
    public class MatrixTest
    {
        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// A test for Identity.
        /// </summary>
        //-----------------------------------------------------------------------------------------
        [TestMethod]
        public void IdentityTest()
        {
            Matrix expected = new Matrix(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
            Matrix actual = Matrix.Identity;

            Assert.AreEqual(expected, actual);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// A test for TranslationPart.
        /// </summary>
        //-----------------------------------------------------------------------------------------
        [TestMethod]
        public void TranslationPartTest()
        {
            float x = 5.2f;
            float y = -95.3254f;
            float z = 47.05f;

            Matrix target = Matrix.Translation(new Vector3(x, y, z));
            
            Vector3 actual = target.TranslationPart;
            Vector3 expected = new Vector3(x, y, z);

            Assert.AreEqual(expected, actual);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// A test for Determinant.
        /// </summary>
        //-----------------------------------------------------------------------------------------
        [TestMethod]
        public void DeterminantTest()
        {
            Matrix target = Matrix.RotationX(0.5f);

            float expected = target.Element00 * (target.Element11 * target.Element22 - target.Element12 * target.Element21) + target.Element01 * (target.Element12 * target.Element20 - target.Element10 * target.Element22) + target.Element02 * (target.Element10 * target.Element21 - target.Element11 * target.Element20);
            float actual = target.Determinant;

            Assert.AreEqual(expected, actual);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// A test for Translation.
        /// </summary>
        //-----------------------------------------------------------------------------------------
        [TestMethod]
        public void TranslationTest()
        {
            float x = 5.2f;
            float y = -95.3254f;
            float z = 47.05f;

            Matrix expected = new Matrix(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x, y, z, 1);
            Matrix target = Matrix.Translation(new Vector3(x, y, z));

            Assert.AreEqual(expected, target);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// A test for RotationX.
        /// </summary>
        //-----------------------------------------------------------------------------------------
        [TestMethod]
        public void RotationXTest()
        {
            float angle = 3.14f;

            float cosAngle = (float) System.Math.Cos(angle);
            float sinAngle = (float) System.Math.Sin(angle);

            Matrix expected = new Matrix(1, 0, 0, 0, 0, cosAngle, sinAngle, 0, 0, -sinAngle, cosAngle, 0, 0, 0, 0, 1);
            Matrix actual = Matrix.RotationX(angle);

            Assert.AreEqual(expected, actual);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// A test for RotationY.
        /// </summary>
        //-----------------------------------------------------------------------------------------
        [TestMethod]
        public void RotationYTest()
        {
            float angle = 3.14f;

            float cosAngle = (float) System.Math.Cos(angle);
            float sinAngle = (float) System.Math.Sin(angle);

            Matrix expected = new Matrix(cosAngle, 0, -sinAngle, 0, 0, 1, 0, 0, sinAngle, 0, cosAngle, 0, 0, 0, 0, 1);
            Matrix actual = Matrix.RotationY(angle);

            Assert.AreEqual(expected, actual);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// A test for RotationZ.
        /// </summary>
        //-----------------------------------------------------------------------------------------
        [TestMethod]
        public void RotationZTest()
        {
            float angle = 3.14f;

            float cosAngle = (float) System.Math.Cos(angle);
            float sinAngle = (float) System.Math.Sin(angle);

            Matrix expected = new Matrix(cosAngle, sinAngle, 0, 0, -sinAngle, cosAngle, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
            Matrix actual = Matrix.RotationZ(angle);

            Assert.AreEqual(expected, actual);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// A test for ReflectX.
        /// </summary>
        //-----------------------------------------------------------------------------------------
        [TestMethod]
        public void ReflectXTest()
        {
            Matrix expected = new Matrix(-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
            Matrix actual = Matrix.ReflectX();

            Assert.AreEqual(expected, actual);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// A test for ReflectY.
        /// </summary>
        //-----------------------------------------------------------------------------------------
        [TestMethod]
        public void ReflectYTest()
        {
            Matrix expected = new Matrix(1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
            Matrix actual = Matrix.ReflectY();

            Assert.AreEqual(expected, actual);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// A test for ReflectZ.
        /// </summary>
        //-----------------------------------------------------------------------------------------
        [TestMethod]
        public void ReflectZTest()
        {
            Matrix expected = new Matrix(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1);
            Matrix actual = Matrix.ReflectZ();

            Assert.AreEqual(expected, actual);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// A test for Scale.
        /// </summary>
        //-----------------------------------------------------------------------------------------
        [TestMethod]
        public void ScaleTest()
        {
            float scale = 2.5f;

            Matrix expected = new Matrix(scale, 0, 0, 0, 0, scale, 0, 0, 0, 0, scale, 0, 0, 0, 0, 1);
            Matrix actual = Matrix.Scale(scale);

            Assert.AreEqual(expected, actual);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// A test for ScaleX.
        /// </summary>
        //-----------------------------------------------------------------------------------------
        [TestMethod]
        public void ScaleXTest()
        {
            float scale = 2.5f;

            Matrix expected = new Matrix(scale, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
            Matrix actual = Matrix.ScaleX(scale);

            Assert.AreEqual(expected, actual);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// A test for ScaleY.
        /// </summary>
        //-----------------------------------------------------------------------------------------
        [TestMethod]
        public void ScaleYTest()
        {
            float scale = 2.5f;

            Matrix expected = new Matrix(1, 0, 0, 0, 0, scale, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
            Matrix actual = Matrix.ScaleY(scale);

            Assert.AreEqual(expected, actual);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// A test for ScaleZ.
        /// </summary>
        //-----------------------------------------------------------------------------------------
        [TestMethod]
        public void ScaleZTest()
        {
            float scale = 2.5f;

            Matrix expected = new Matrix(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, scale, 0, 0, 0, 0, 1);
            Matrix actual = Matrix.ScaleZ(scale);

            Assert.AreEqual(expected, actual);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// A test for LookAt.
        /// </summary>
        //-----------------------------------------------------------------------------------------
        [TestMethod]
        public void LookAtTest()
        {
            Vector3 cameraPosition = new Vector3(40, -32.5f, -98.0025f);
            Vector3 cameraTarget = new Vector3(1.0f, -5.2f, 9.35f);
            Vector3 cameraUpVector = new Vector3(0.0f, 1.0f, 0.0f);

            Vector3 zAxis = (cameraTarget - cameraPosition).Normalize();
            Vector3 xAxis = Vector3.CrossProduct(cameraUpVector, zAxis).Normalize();
            Vector3 yAxis = Vector3.CrossProduct(zAxis, xAxis).Normalize();

            Matrix expected = new Matrix(xAxis.X, yAxis.X, zAxis.X, 0, xAxis.Y, yAxis.Y, zAxis.Y, 0, xAxis.Z, yAxis.Z, zAxis.Z, 0, -(xAxis * cameraPosition), -(yAxis * cameraPosition), -(zAxis * cameraPosition), 1);
            Matrix actual = Matrix.LookAt(cameraPosition, cameraTarget, cameraUpVector);
            
            Assert.AreEqual(expected, actual);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// A test for PerspectiveFov.
        /// </summary>
        //-----------------------------------------------------------------------------------------
        [TestMethod]
        public void PerspectiveFovTest()
        {
            float fieldOfViewY = 3.14f;
            float aspectRatio = 1.25f;
            float minPlaneZ = 10.5f;
            float maxPlaneZ = 514.23f;

            float height = 1.0f / (float) System.Math.Tan(fieldOfViewY / 2.0f);

            Matrix expected = new Matrix(height / aspectRatio, 0, 0, 0, 0, height, 0, 0, 0, 0, maxPlaneZ / (maxPlaneZ - minPlaneZ), 1, 0, 0, -minPlaneZ * maxPlaneZ / (maxPlaneZ - minPlaneZ), 0);
            Matrix actual = Matrix.PerspectiveFov(fieldOfViewY, aspectRatio, minPlaneZ, maxPlaneZ);

            Assert.AreEqual(expected, actual);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// A test for Ortho.
        /// </summary>
        //-----------------------------------------------------------------------------------------
        [TestMethod]
        public void OrthoTest()
        {
            float width = 640.0f;
            float height = 480.0f;
            float minPlaneZ = 0.01f;
            float maxPlaneZ = 2.0f;

            Matrix expected = new Matrix(2.0f / width, 0, 0, 0, 0, 2.0f / height, 0, 0, 0, 0, 1.0f / (maxPlaneZ - minPlaneZ), 0, 0, 0, minPlaneZ / (minPlaneZ - maxPlaneZ), 1);
            Matrix actual = Matrix.Ortho(width, height, minPlaneZ, maxPlaneZ);

            Assert.AreEqual(expected, actual);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// A test for OrthoOffCenter.
        /// </summary>
        //-----------------------------------------------------------------------------------------
        [TestMethod]
        public void OrthoOffCenterTest()
        {
            float minPlaneX = 15.0f;
            float maxPlaneX = 640.0f;
            float minPlaneY = 5.0f;
            float maxPlaneY = 480.0f;
            float minPlaneZ = 0.01f;
            float maxPlaneZ = 10.0f;

            Matrix expected = new Matrix(2.0f / (maxPlaneX - minPlaneX), 0, 0, 0, 0, -2.0f / (maxPlaneY - minPlaneY), 0, 0, 0, 0, 1.0f / (maxPlaneZ - minPlaneZ), 0, (minPlaneX + maxPlaneX) / (minPlaneX - maxPlaneX), -(minPlaneY + maxPlaneY) / (minPlaneY - maxPlaneY), minPlaneZ / (minPlaneZ - maxPlaneZ), 1);
            Matrix actual = Matrix.OrthoOffCenter(minPlaneX, maxPlaneX, minPlaneY, maxPlaneY, minPlaneZ, maxPlaneZ);

            Assert.AreEqual(expected, actual);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// A test for Inverse.
        /// </summary>
        //-----------------------------------------------------------------------------------------
        [TestMethod]
        public void InverseTest()
        {
            Matrix target = Matrix.Scale(2.0f);

            Matrix expected = Matrix.Scale(0.5f);
            Matrix actual = target.Inverse();
            
            Assert.AreEqual(expected, actual);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// A test for Multiply Vector.
        /// </summary>
        //-----------------------------------------------------------------------------------------
        [TestMethod]
        public void MultiplyVectorTest()
        {
            float x = 1.5f;
            float y = -8.2f;
            float z = 3.45f;
            float scale = 5.0f;

            Vector3 vector = new Vector3(x, y, z);
            Matrix matrix = Matrix.Scale(scale);

            Vector3 expected = new Vector3(x * scale, y * scale, z * scale);
            Vector3 actual = Matrix.Multiply(vector, matrix);

            Assert.AreEqual(expected, actual);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// A test for Multiply Vector operator.
        /// </summary>
        //-----------------------------------------------------------------------------------------
        [TestMethod]
        public void op_MultiplyVectorTest()
        {
            float x = 1.5f;
            float y = -8.2f;
            float z = 3.45f;
            float scale = 5.0f;

            Vector3 vector = new Vector3(x, y, z);
            Matrix matrix = Matrix.Scale(scale);

            Vector3 expected = new Vector3(x * scale, y * scale, z * scale);
            Vector3 actual = vector * matrix;

            Assert.AreEqual(expected, actual);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// A test for MultiplyDirection.
        /// </summary>
        //-----------------------------------------------------------------------------------------
        [TestMethod]
        public void MultiplyDirectionVectorTest()
        {
            Vector3 vector = new Vector3(1.5f, -8.2f, 3.45f);
            Matrix matrix = Matrix.Scale(5.0f) * Matrix.Translation(new Vector3(8.4f, -1.0f, 3.6f));
            
            Vector3 actual = Matrix.MultiplyDirection(vector, matrix);

            Assert.AreEqual(vector * Matrix.Scale(5.0f), actual);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// A test for Multiply Ray.
        /// </summary>
        //-----------------------------------------------------------------------------------------
        [TestMethod]
        public void MultiplyRayTest()
        {
            Vector3 origin = new Vector3(1.5f, -8.2f, 3.45f);
            Vector3 direction = new Vector3(1.5f, -2.3f, 5.6f).Normalize();

            float scale = 5.0f;
            Vector3 translation = new Vector3(8.4f, -1.0f, 3.6f);

            Ray ray = new Ray(origin, direction);
            Matrix matrix = Matrix.Scale(scale) * Matrix.Translation(translation);

            Ray expected = new Ray(new Vector3(origin.X * scale + translation.X, origin.Y * scale + translation.Y, origin.Z * scale + translation.Z), new Vector3(direction.X * scale, direction.Y * scale, direction.Z * scale));
            Ray actual = Matrix.Multiply(ray, matrix);

            Assert.AreEqual(expected, actual);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// A test for Multiply Ray Operator.
        /// </summary>
        //-----------------------------------------------------------------------------------------
        [TestMethod]
        public void op_MultiplyRayTest()
        {
            Vector3 origin = new Vector3(1.5f, -8.2f, 3.45f);
            Vector3 direction = new Vector3(1.5f, -2.3f, 5.6f).Normalize();

            float scale = 5.0f;
            Vector3 translation = new Vector3(8.4f, -1.0f, 3.6f);

            Ray ray = new Ray(origin, direction);
            Matrix matrix = Matrix.Scale(scale) * Matrix.Translation(translation);

            Ray expected = new Ray(new Vector3(origin.X * scale + translation.X, origin.Y * scale + translation.Y, origin.Z * scale + translation.Z), new Vector3(direction.X * scale, direction.Y * scale, direction.Z * scale));
            Ray actual = ray * matrix;

            Assert.AreEqual(expected, actual);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// A test for Matrix Multiply.
        /// </summary>
        //-----------------------------------------------------------------------------------------
        [TestMethod]
        public void MultiplyTest()
        {
            Matrix matrix1 = Matrix.Scale(5.0f);
            Matrix matrix2 = Matrix.Translation(new Vector3(8.4f, -1.0f, 3.6f));

            Matrix expected = new Matrix(5.0f, 0, 0, 0, 0, 5.0f, 0, 0, 0, 0, 5.0f, 0, 8.4f, -1.0f, 3.6f, 1);
            Matrix actual = Matrix.Multiply(matrix1, matrix2);

            Assert.AreEqual(expected, actual);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// A test for Matrix Multiply operator.
        /// </summary>
        //-----------------------------------------------------------------------------------------
        [TestMethod]
        public void op_MultiplyTest()
        {
            Matrix matrix1 = Matrix.Scale(5.0f);
            Matrix matrix2 = Matrix.Translation(new Vector3(8.4f, -1.0f, 3.6f));

            Matrix expected = new Matrix(5.0f, 0, 0, 0, 0, 5.0f, 0, 0, 0, 0, 5.0f, 0, 8.4f, -1.0f, 3.6f, 1);
            Matrix actual = Matrix.Multiply(matrix1, matrix2);

            Assert.AreEqual(expected, actual);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// A test for Equals is True.
        /// </summary>
        //-----------------------------------------------------------------------------------------
        [TestMethod]
        public void EqualsTrueTest()
        {
            Matrix target = Matrix.RotationX(3.14f);
            Matrix other = Matrix.RotationX(3.14f);

            bool expected = true;
            bool actual = target.Equals(other);

            Assert.AreEqual(expected, actual);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// A test for Equals is False.
        /// </summary>
        //-----------------------------------------------------------------------------------------
        [TestMethod]
        public void EqualsFalseTest()
        {
            Matrix target = Matrix.RotationX(3.14f);
            Matrix other = Matrix.RotationX(3.1f);

            bool expected = false;
            bool actual = target.Equals(other);

            Assert.AreEqual(expected, actual);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// A test for Equals with a null value.
        /// </summary>
        //-----------------------------------------------------------------------------------------
        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void EqualsNullTest()
        {
            Matrix target = Matrix.RotationX(3.14f);
            Matrix other = null;

            bool actual = target.Equals(other);
        }
    }
}
